#include "PI.h"

ostream& operator<<(std::ostream& os, const point3d p){
        //os << "(" << p.x << ", " << p.y << ", " << p.z << ")";
        os << "n" << p.z << "_" << p.x << "_" << p.y;
        return os;
}

int main(int argc, char* argv[]){
        
        if(argc != 2){
                cout << "Usage: ./PI bench.spice" << endl;
                exit(0);
        }
                
        ifstream fin;
        fin.open(argv[1]);
        if(!fin)
        {
                cout<<"Fail to open input file"<<endl;
                return 1;
        }
        cout << "Parsing benchmark...." << endl;
        
        string benchname = argv[1];
        cout << benchname << endl;
        
        Graph G;
        Edge tmp_E;
        
        bool dir;//for cs
        string token;
        unsigned int found;
        point3d tmp_pnt, tmp_pnt_;
        
        double* voltage = new double[1];
        double* resistance = new double[1];
        double* current = new double[1];
        
        unsigned int i, j, pos;
        char line_content[65536];
        while(!fin.eof()){
                fin.getline(line_content, 65536);
                string temp = line_content;
                
                switch(temp.c_str()[0]){
                        case 'r': pos = 0; i = 1;
                                while(temp.c_str()[i++] != ' ');
                                token = temp.substr(pos, i - pos - 1);
                                tmp_E.name = token;
                                tmp_E.et = VOLTVIA;
                                
                                i++;
                                tmp_pnt.z = temp.c_str()[i] - '0';
                                
                                i += 2;
                                pos = i;
                                while(temp.c_str()[i++] != '_');
                                token = temp.substr(pos, i - pos - 1);
                                tmp_pnt.x = atoi(token.c_str());
                                
                                pos = i;
                                while(temp.c_str()[i++] != ' ');
                                token = temp.substr(pos, i - pos - 1);
                                tmp_pnt.y = atoi(token.c_str());
                                
                                while(temp.c_str()[i++] != ' ');
                                
                                pos = i;
                                while(temp.c_str()[i] != '\0'){i++;}
                                token = temp.substr(pos, i - pos);
                                
                                my_atof(token.c_str(), resistance);
                                tmp_E.res = *resistance;
                                //cout << tmp_E.res << endl;
                                
                                tmp_E.beg_pnt = tmp_pnt;
                                tmp_E.end_pnt = tmp_pnt;
                                
                                if(G.nodes.find(tmp_pnt) == G.nodes.end()){
                                        vector<Edge> TE;
                                        TE.push_back(tmp_E);
                                        G.nodes.insert(make_pair(tmp_pnt, TE));
                                }
                                else
                                        G.nodes[tmp_pnt].push_back(tmp_E);
                                
                                //cout << tmp_pnt << endl;
                                break;
                                
                        case 'v': pos = 0; i = 1;
                                while(temp.c_str()[i++] != ' ');
                                
                                while(temp.c_str()[i++] != 'n');
                                tmp_pnt.z = temp.c_str()[i] - '0';
                                
                                i += 2;
                                pos = i;
                                while(temp.c_str()[i++] != '_');
                                token = temp.substr(pos, i - pos - 1);
                                tmp_pnt.x = atoi(token.c_str());
                                
                                pos = i;
                                while(temp.c_str()[i++] != ' ');
                                token = temp.substr(pos, i - pos - 1);
                                tmp_pnt.y = atoi(token.c_str());
                                
                                i += 2;//separate
                                
                                pos = i;
                                while(temp.c_str()[i] != '\0'){i++;}
                                token = temp.substr(pos, i - pos);
                                my_atof(token.c_str(), voltage);
                                
                                G.vs.insert(make_pair(tmp_pnt, *voltage));
                                
                                //cout << tmp_pnt << endl;
                                break;
                                
                        case 'R': pos = 0; i = 1;
                                while(temp.c_str()[i++] != ' ');
                                token = temp.substr(pos, i - pos - 1);
                                tmp_E.name = token;
                                tmp_E.et = NORMAL;
                                
                                i++;
                                tmp_pnt.z = temp.c_str()[i] - '0';
                                
                                i += 2;
                                pos = i;
                                while(temp.c_str()[i++] != '_');
                                token = temp.substr(pos, i - pos - 1);
                                tmp_pnt.x = atoi(token.c_str());
                                
                                pos = i;
                                while(temp.c_str()[i++] != ' ');
                                token = temp.substr(pos, i - pos - 1);
                                tmp_pnt.y = atoi(token.c_str());
                                
                                i++;
                                tmp_pnt_.z = temp.c_str()[i] - '0';
                                
                                i += 2;
                                pos = i;
                                while(temp.c_str()[i++] != '_');
                                token = temp.substr(pos, i - pos - 1);
                                tmp_pnt_.x = atoi(token.c_str());
                                
                                pos = i;
                                while(temp.c_str()[i++] != ' ');
                                token = temp.substr(pos, i - pos - 1);
                                tmp_pnt_.y = atoi(token.c_str());
                                
                                tmp_E.beg_pnt = tmp_pnt;
                                tmp_E.end_pnt = tmp_pnt_;
                                
                                pos = i;
                                while(temp.c_str()[i] != '\0'){i++;}
                                token = temp.substr(pos, i - pos);
                                
                                my_atof(token.c_str(), resistance);
                                tmp_E.res = *resistance;
                                //cout << tmp_E.res << endl;

                                if(G.nodes.find(tmp_pnt) == G.nodes.end()){
                                        vector<Edge> TE;
                                        TE.push_back(tmp_E);
                                        G.nodes.insert(make_pair(tmp_pnt, TE));
                                }
                                else
                                        G.nodes[tmp_pnt].push_back(tmp_E);
                                
                                //Reverse direction
                                tmp_E.beg_pnt = tmp_pnt_;
                                tmp_E.end_pnt = tmp_pnt;
                                
                                if(G.nodes.find(tmp_pnt_) == G.nodes.end()){
                                        vector<Edge> TE;
                                        TE.push_back(tmp_E);
                                        G.nodes.insert(make_pair(tmp_pnt_, TE));
                                }
                                else
                                        G.nodes[tmp_pnt_].push_back(tmp_E);
                                
                                //cout << tmp_pnt << endl;
                                //cout << tmp_pnt_ << endl;
                                break;
                                
                        case 'V': pos = 0; i = 1;
                                while(temp.c_str()[i++] != ' ');
                                token = temp.substr(pos, i - pos - 1);
                                tmp_E.name = token;
                                tmp_E.et = METALVIA;
                                
                                i++;
                                tmp_pnt.z = temp.c_str()[i] - '0';
                                
                                i += 2;
                                pos = i;
                                while(temp.c_str()[i++] != '_');
                                token = temp.substr(pos, i - pos - 1);
                                tmp_pnt.x = atoi(token.c_str());
                                
                                pos = i;
                                while(temp.c_str()[i++] != ' ');
                                token = temp.substr(pos, i - pos - 1);
                                tmp_pnt.y = atoi(token.c_str());
                                
                                i++;
                                tmp_pnt_.z = temp.c_str()[i] - '0';
                                tmp_pnt_.x = tmp_pnt.x;
                                tmp_pnt_.y = tmp_pnt.y;
                                
                                //directly to the resistence value
                                while(temp.c_str()[i++] != ' ');
                                
                                pos = i;
                                while(temp.c_str()[i] != '\0'){i++;}
                                token = temp.substr(pos, i - pos);
                                
                                my_atof(token.c_str(), resistance);
                                tmp_E.res = *resistance;
                                //cout << tmp_E.res << endl;
                                
                                tmp_E.beg_pnt = tmp_pnt;
                                tmp_E.end_pnt = tmp_pnt_;
                                
                                if(G.nodes.find(tmp_pnt) == G.nodes.end()){
                                        vector<Edge> TE;
                                        TE.push_back(tmp_E);
                                        G.nodes.insert(make_pair(tmp_pnt, TE));
                                }
                                else
                                        G.nodes[tmp_pnt].push_back(tmp_E);
                                
                                //Reverse direction
                                tmp_E.beg_pnt = tmp_pnt_;
                                tmp_E.end_pnt = tmp_pnt;
                                
                                if(G.nodes.find(tmp_pnt_) == G.nodes.end()){
                                        vector<Edge> TE;
                                        TE.push_back(tmp_E);
                                        G.nodes.insert(make_pair(tmp_pnt_, TE));
                                }
                                else
                                        G.nodes[tmp_pnt_].push_back(tmp_E);
                                
                                //cout << tmp_pnt << endl;
                                //cout << tmp_pnt_ << endl;
                                break;
                                
                        case 'i': pos = 0; i = 1;
                                while(temp.c_str()[i++] != ' ');
                                token = temp.substr(pos, i - pos - 1);
                                tmp_E.name = token;
                                tmp_E.et = CS;
                                
                                if(temp.c_str()[i] == 'n'){//vdd -> ground current
                                        i++;
                                        tmp_pnt.z = temp.c_str()[i] - '0';
                                        
                                        i += 2;
                                        pos = i;
                                        while(temp.c_str()[i++] != '_');
                                        token = temp.substr(pos, i - pos - 1);
                                        tmp_pnt.x = atoi(token.c_str());
                                        
                                        pos = i;
                                        while(temp.c_str()[i++] != ' ');
                                        token = temp.substr(pos, i - pos - 1);
                                        tmp_pnt.y = atoi(token.c_str());
                                        
                                        i += 2;
                                        dir = true;
                                }
                                else{
                                        assert(temp.c_str()[i] == '0');//vss -> ground current
                                        i += 3;
                                        tmp_pnt.z = temp.c_str()[i] - '0';
                                        i += 2;
                                        pos = i;
                                        while(temp.c_str()[i++] != '_');
                                        token = temp.substr(pos, i - pos - 1);
                                        tmp_pnt.x = atoi(token.c_str());
                                        
                                        pos = i;
                                        while(temp.c_str()[i++] != ' ');
                                        token = temp.substr(pos, i - pos - 1);
                                        tmp_pnt.y = atoi(token.c_str());
                                        
                                        dir = false;
                                }
                                
                                tmp_E.beg_pnt = tmp_pnt;
                                tmp_E.end_pnt = tmp_pnt;
                                
                                pos = i;
                                while(temp.c_str()[i] != '\0'){i++;}
                                token = temp.substr(pos, i - pos);
                                my_atof(token.c_str(), current);
                                
                                if(dir == true)
                                        tmp_E.res = *current;
                                else
                                        tmp_E.res = *current * (-1.0);
                                
                                //cout << tmp_E.res << endl;
                                if(G.nodes.find(tmp_pnt) == G.nodes.end()){
                                        vector<Edge> TE;
                                        TE.push_back(tmp_E);
                                        G.nodes.insert(make_pair(tmp_pnt, TE));
                                }
                                else
                                        G.nodes[tmp_pnt].push_back(tmp_E);
                                
                                G.cs.insert(make_pair(tmp_pnt, *current));
                                
                                //cout << tmp_pnt << endl;
                                break;
                                
                        case '.': if(temp.substr(1, 3) == "end")
                                        goto OUTLET;
                                break;
                                
                        case '*': break;
                                
                        default: cout << "Never seen such line!" << endl; break;
                }
        }
        
  OUTLET:
        cout << "Benchmark reading finished!" << endl;
        cout << "Writing kcl & kvl equation..." << endl;
        
        map<point3d, int> nodePos;
        map<point3d, vector<Edge> >::iterator it_n;
        
        //long row_cnt = 1;//CHOLMOD
        long row_cnt = 0;//UMFPACK
        long est_Mtx_A_row_cnt = 0;
        
        found = benchname.find(".spice");
        benchname.replace(benchname.begin() + found, benchname.end(), ".node.idx");
        
        ofstream fout(benchname.c_str());
        for(it_n = G.nodes.begin(); it_n != G.nodes.end(); it_n++){
                fout << (*it_n).first << endl;
                nodePos[(*it_n).first] = row_cnt++;
                est_Mtx_A_row_cnt += (*it_n).second.size();
        }
        fout.close();
        
        /***************************************************************************/
        // The order of V is fixed, the key is to move the position of G coefficients 
        // now starts writing equation
        /***************************************************************************/
        
        const long Dimention = G.nodes.size();
        cout << "Dimention is " << Dimention << endl;
        cout << "Estimated row count in Matrix_A is " << est_Mtx_A_row_cnt << endl;
        
        vector<SpMtrDt> Matrix_A;
        double Matrix_B[Dimention];
        memset(Matrix_B, 0.0, sizeof(double) * Dimention);
        
        //row_cnt = 1;//CHOLMOD
        row_cnt = 0;//UMFPACK
        vector<pair<point3d, point3d> > vias;
        vector<pair<point3d, point3d> >::iterator it_vias;
        
        for(it_n = G.nodes.begin(); it_n != G.nodes.end(); it_n++){
                
                int flag = 0;
                for(it_vias = vias.begin(); it_vias != vias.end(); it_vias++){
                        if( (*it_vias).first == (*it_n).first || (*it_vias).second == (*it_n).first){
                                flag = 1;
                                break;
                        }
                }
                if(flag == 1)                        
                        continue;
                
                double tot_res = 0.0;
                for(j = 0; j < (*it_n).second.size(); j++){//lower layer to higher 
                        if((*it_n).second[j].et == NORMAL){
                                SpMtrDt mySMD;
                                mySMD.x = row_cnt;
                                mySMD.y = nodePos[(*it_n).second[j].end_pnt];
                                mySMD.val = 1.0 / (*it_n).second[j].res;
                                if((*it_n).second[j].res < 0.00001)
                                        cout << "Treated as via! " << (*it_n).first << endl;
                                
                                Matrix_A.push_back(mySMD);
                                tot_res += mySMD.val;
                        }
                        else if((*it_n).second[j].et == METALVIA){
                                if((*it_n).second[j].res - 0.0001 <= 0.00001){
                                        point3d new_n = (*it_n).second[j].end_pnt;
                                        vias.push_back(make_pair((*it_n).second[j].end_pnt, (*it_n).second[j].beg_pnt));
                                        for(unsigned int k = 0; k < G.nodes[new_n].size(); k++){
                                                if((*it_n).second[j].beg_pnt == G.nodes[new_n][k].end_pnt)
                                                        continue;
                                                
                                                if(G.nodes[new_n][k].et != NORMAL){
                                                        assert(G.nodes[new_n][k].et == 0);//volt via
                                                        //cout << "hey " << G.nodes[new_n][k].et << " " << G.nodes[new_n][k].beg_pnt 
                                                        //<< " " << G.nodes[new_n][k].end_pnt << " " << (*it_n).second[j].beg_pnt << " " << (*it_n).second[j].end_pnt << endl;
                                                        Matrix_B[row_cnt] += -1.0 * (1.0 / G.nodes[new_n][k].res) * G.vs[G.nodes[new_n][k].end_pnt];  
                                                        tot_res += 1.0 / G.nodes[new_n][k].res;
                                                        if(G.nodes[new_n][k].res < 0.00001)
                                                                cout << "Treated as via! " << new_n << endl;
                                                        continue;
                                                }
                                                
                                                SpMtrDt mySMD;
                                                mySMD.x = row_cnt;
                                                mySMD.y = nodePos[G.nodes[new_n][k].end_pnt];
                                                mySMD.val = 1.0 / G.nodes[new_n][k].res;
                                                if(G.nodes[new_n][k].res < 0.00001)
                                                        cout << "huhu " << new_n << endl;
                                                Matrix_A.push_back(mySMD);
                                                tot_res += mySMD.val;
                                        }
                                }
                                else{
                                        cout << "Non-zero metal via found!" << endl;
                                        SpMtrDt mySMD;
                                        mySMD.x = row_cnt;
                                        mySMD.y = nodePos[(*it_n).second[j].end_pnt];
                                        mySMD.val = 1.0 / (*it_n).second[j].res;
                                        if((*it_n).second[j].res < 0.00001)
                                                cout << "Treated as via! " << (*it_n).first << endl;
                                        Matrix_A.push_back(mySMD);
                                        tot_res += mySMD.val;
                                }
                        }
                        else if((*it_n).second[j].et == VOLTVIA){
                                Matrix_B[row_cnt] += -1.0 * (1.0 / (*it_n).second[j].res) * G.vs[(*it_n).second[j].end_pnt];  
                                tot_res += 1.0 / (*it_n).second[j].res;
                                if((*it_n).second[j].res < 0.00001)
                                        cout << "Treated as via! " << (*it_n).first << endl;
                        }
                        else if((*it_n).second[j].et == CS){
                                Matrix_B[row_cnt] += (*it_n).second[j].res;  //this res is current value!
                        }
                        else
                                cout << "Unknown edge type!!!" << endl;
                }
                
                SpMtrDt mySMD;
                mySMD.x = row_cnt;
                mySMD.y = nodePos[(*it_n).first];
                mySMD.val = -1.0 * tot_res;
                //if(tot_res > 3.0){
                        //cout << "biu " << (*it_n).first << endl;
                        //cout << tot_res << endl;
                        //cin.get();
                //}
                Matrix_A.push_back(mySMD);
                row_cnt++;
        }
        
        //cin.get();
        //for vias
        cout << "Voltage sources number is " << G.vs.size() << endl;
        cout << "Current sources number is " << G.cs.size() << endl;
        cout << "Via count is " << vias.size() << endl;
        for(it_vias = vias.begin(); it_vias != vias.end(); it_vias++){
                SpMtrDt mySMD;
                mySMD.x = row_cnt;
                mySMD.y = nodePos[(*it_vias).first];
                mySMD.val = 1.0;
                Matrix_A.push_back(mySMD);
                
                mySMD.y = nodePos[(*it_vias).second];
                mySMD.val = -1.0;
                Matrix_A.push_back(mySMD);
                row_cnt++;
        }
        
        //assert(row_cnt - 1 == Dimention); //CHOLMOD
        assert(row_cnt == Dimention);
        
        //write to file
        cout << "Writing to file..." << endl;
        benchname = argv[1];
        found = benchname.find(".spice");
        benchname.replace(benchname.begin() + found, benchname.end(), ".A_info");
        cout << "A's general info file name is " << benchname << endl;
        
        ofstream out(benchname.c_str());
        out << Dimention << endl; 
        out << Matrix_A.size() << endl;
        out.close();


        vector<SpMtrDt>::iterator it_Mtx_A;
        benchname = argv[1];
        found = benchname.find(".spice");
        benchname.replace(benchname.begin() + found, benchname.end(), ".A_row");
        cout << "A's row file name is " << benchname << endl;
        ofstream rout(benchname.c_str());
        
        benchname = argv[1];
        found = benchname.find(".spice");
        benchname.replace(benchname.begin() + found, benchname.end(), ".A_col");
        cout << "A's column file name is " << benchname << endl;
        ofstream colout(benchname.c_str());
        
        benchname = argv[1];
        found = benchname.find(".spice");
        benchname.replace(benchname.begin() + found, benchname.end(), ".A_val");
        cout << "A's val file name is " << benchname << endl;
        ofstream vout(benchname.c_str());
        for(it_Mtx_A = Matrix_A.begin(); it_Mtx_A != Matrix_A.end(); it_Mtx_A++){
                rout << it_Mtx_A->x << endl;
                colout << it_Mtx_A->y << endl;
                vout << it_Mtx_A->val << endl;
        }
        
        rout.close();
        colout.close();
        vout.close();
        
        benchname = argv[1];
        found = benchname.find(".spice");
        benchname.replace(benchname.begin() + found, benchname.end(), ".B");
        cout << "B's file name is " << benchname << endl;
        ofstream kout(benchname.c_str());
        for(i = 0; i < Dimention; i++)
                kout << Matrix_B[i] << endl;
        
        kout.close();
        
        cout << "Done!" << endl;
        return 0;
}
